﻿using System;
using System.Collections.Generic;
#if CONTRACTS_FULL
using System.Diagnostics.Contracts;
using System.Linq;
#endif

namespace Elca.MvvmHelpers {

    /// <summary>
    /// A registry of services, i.e. a simple Inversion of Control implementation
    /// </summary>
    public static class ServiceRegistry {

        private static readonly Dictionary<Type, ServiceInfo> s_map = new Dictionary<Type, ServiceInfo>();

        /// <summary>
        /// Inserts info in the registry
        /// </summary>
        /// <param name="serviceInterfaceType">The interface implemented by the service</param>
        /// <param name="serviceImplementationType">The class implementing the interface</param>
        /// <param name="lifetime">The lifetime type</param>
        public static void Register(Type serviceInterfaceType, Type serviceImplementationType, ServiceLifetime lifetime) {

#if CONTRACTS_FULL
            Contract.Requires(serviceInterfaceType != null);
            Contract.Requires(serviceImplementationType != null);
// ReSharper disable PossibleNullReferenceException
            Contract.Requires(serviceImplementationType.GetInterfaces().Any(t => t == serviceInterfaceType));
// ReSharper restore PossibleNullReferenceException
// ReSharper disable AssignNullToNotNullAttribute
            Contract.Ensures(s_map[serviceInterfaceType] != null);
// ReSharper restore AssignNullToNotNullAttribute
#endif
            ServiceInfo si;

            if (s_map.TryGetValue(serviceInterfaceType, out si)) {

                // we know about serviceInterfaceType already

                if (si.Lifetime != lifetime || si.ImplementationType != serviceImplementationType) {
                    
                    throw new InvalidOperationException("There is already an instance for the service interface " +
                                                        serviceInterfaceType.Name);
                }
            } else {

                si = new ServiceInfo {
                    Lifetime = lifetime,
                    ImplementationType = serviceImplementationType,
                };
                s_map[serviceInterfaceType] = si;
            }
        }

        /// <summary>
        /// Generic version of <c>Register</c> with 3 parameters
        /// </summary>
        /// <typeparam name="TServiceInterface">The interface implemented by the service</typeparam>
        /// <param name="serviceImplementationType">The class implementing the interface</param>
        /// <param name="lifetime">The lifetime type</param>
        public static void Register<TServiceInterface> (Type serviceImplementationType, ServiceLifetime lifetime) 
            where TServiceInterface : class {
            
#if CONTRACTS_FULL
            Contract.Requires(serviceImplementationType != null);
// ReSharper disable PossibleNullReferenceException
            Contract.Requires(serviceImplementationType.GetInterfaces().Any(t => t.Equals(typeof(TServiceInterface))));
// ReSharper restore PossibleNullReferenceException
#endif
            Register(typeof(TServiceInterface), serviceImplementationType, lifetime);
        }

        /// <summary>
        /// Simpler version of <c>Register</c>, for a singleton
        /// </summary>
        /// <param name="serviceInterfaceType">The interface implemented by the service</param>
        /// <param name="serviceInstance">An object instance implementing the interface</param>
        public static void RegisterSingleton(Type serviceInterfaceType, object serviceInstance) {

#if CONTRACTS_FULL
            Contract.Requires(serviceInterfaceType != null);
            Contract.Requires(serviceInstance != null);
// ReSharper disable PossibleNullReferenceException
            Contract.Requires(serviceInstance.GetType().GetInterfaces().Any(t => t == serviceInterfaceType));
// ReSharper restore PossibleNullReferenceException
// ReSharper disable AssignNullToNotNullAttribute
            Contract.Ensures(s_map[serviceInterfaceType] != null);
// ReSharper restore AssignNullToNotNullAttribute
#endif
            ServiceInfo si;

            if (s_map.TryGetValue(serviceInterfaceType, out si)) {
                
                // we know about serviceInterfaceType already
                // ignore if user is registering the same instance
                if (! Object.ReferenceEquals(si.ImplementationInstance, serviceInstance)) {
                    throw new InvalidOperationException("There is already an instance for the service interface " +
                                                        serviceInterfaceType.Name);
                }
            } else {
                // normal case
                si = new ServiceInfo {
                    Lifetime = ServiceLifetime.Singleton,
                    ImplementationInstance = serviceInstance,
                };
                s_map[serviceInterfaceType] = si;
            }
        }

        /// <summary>
        /// Generic version of <c>RegisterSingleton</c> with 2 parameters
        /// </summary>
        /// <typeparam name="TServiceInterface">The interface implemented by the service</typeparam>
        /// <param name="serviceInstance">An object instance implementing the interface</param>
        public static void RegisterSingleton<TServiceInterface>(object serviceInstance)
            where TServiceInterface : class {

#if CONTRACTS_FULL
            Contract.Requires(serviceInstance != null);
// ReSharper disable PossibleNullReferenceException
            Contract.Requires(serviceInstance.GetType().GetInterfaces().Any(t => t == typeof(TServiceInterface)));
// ReSharper restore PossibleNullReferenceException
#endif
            RegisterSingleton(typeof(TServiceInterface), serviceInstance);
        }

        public static TServiceInterface Resolve<TServiceInterface>() 
            where TServiceInterface : class {

            ServiceInfo si;
            if (s_map.TryGetValue(typeof(TServiceInterface), out si)) {

                if (si.Lifetime == ServiceLifetime.Singleton) {

                    if (si.ImplementationInstance == null) {

                        si.ImplementationInstance = Activator.CreateInstance(si.ImplementationType);
                    }
                    
                    return (TServiceInterface) si.ImplementationInstance;
                }

                // else 'multiple'

                return (TServiceInterface) Activator.CreateInstance(si.ImplementationType);
            }

            throw new InvalidOperationException("Unknown service with interface " + typeof(TServiceInterface).Name);
        }

        /// <summary>
        /// Removes all registrations; useful for unit tests
        /// </summary>
        public static void ClearRegistrations() {
            s_map.Clear();
        }
    }

    /// <summary>
    /// Info about a registered service
    /// </summary>
    public class ServiceInfo {

        /// <summary>
        /// Service lifetime
        /// </summary>
        public ServiceLifetime Lifetime { get; set; }

        /// <summary>
        /// If the service is a singleton, its instance is there
        /// </summary>
        public object ImplementationInstance { get; set; }

        /// <summary>
        /// If the service is 'multiple', its type is there and is used to create instances
        /// If the service is 'singleton' and its type is known, it is stored there (but the instance is used as soon as it is available)
        /// </summary>
        public Type ImplementationType { get; set; }
    }

    /// <summary>
    /// What is the desired lifetime of a service implementation ?
    /// </summary>
    public enum ServiceLifetime {
        
        /// <summary>
        /// Singleton, i.e. all calls to Resolve return the same instance
        /// </summary>
        Singleton,

        /// <summary>
        /// Multiple instances, i.e. all calls to Resolve return a new instance
        /// </summary>
        Multiple,
    }
}